<?php
defined('SYS_RUN') or exit();

class Connector {

    private $_connectionLine = null;

    const RESPONSE_TIMEOUT = 30;

    const PACKET_SIZE = 1024;

    const STREAM_END_LINE = '\n\r';

    const STATUS_SERVER_START_SEND = 'SERVER_START_SEND';

    const STATUS_SERVER_READING = 'SERVER_READING';

    const STATUS_SERVER_END_SEND = 'SERVER_END_SEND';

    const STATUS_CLIENT_READING = 'CLIENT_READING';

    const STATUS_CLIENT_END_SEND = 'CLIENT_END_SEND';

    const STATUS_CLIENT_GOODBYE = 'CLIENT_GOODBYE';

    function __construct() {
        $servers = Settings::get('backendServers');
        $servers = $this->_connectToServers($servers);
        $this->_connectionLine = $this->_createConnectionLine($servers);
    }

    public function send($controller, $action, $data= null) {
        $connect = $this->_selectConnect();

        $data = array(
            'controller'    => $controller,
            'action'        => $action,
            'data'          => $data
        );
        $toServer = $this->_prepareOutput($data);

        $talkStatus = array(
            'endTalk'       => false,
            'startTime'     => time(),
            'sendCounter'   => 0
        );
        $response = '';
        while(!$talkStatus['endTalk']) {
            $input = stream_get_line($connect, 0, self::STREAM_END_LINE);

            switch ($input) {
                case self::STATUS_SERVER_READING:
                    $outputBuffer = &$toServer;
                    $sendCounter = &$talkStatus['sendCounter'];
                    $outputPath = substr($outputBuffer, $sendCounter, self::PACKET_SIZE);
                    if($outputPath) {
                        if($this->_write($connect, $outputPath))
                            $sendCounter += self::PACKET_SIZE;
                    } else {
                        $this->_write($connect, self::STATUS_CLIENT_END_SEND);
                    }
                    break;
                case self::STATUS_SERVER_START_SEND:
                    $this->_write($connect, self::STATUS_CLIENT_READING);
                    break;
                case self::STATUS_SERVER_END_SEND:
                    $this->_write($connect, self::STATUS_CLIENT_GOODBYE);
                    $talkStatus['endTalk'] = true;
                    break;
                default:
                    if((time() - $talkStatus['startTime']) > self::RESPONSE_TIMEOUT) {
//                        $this->disconnect($connect);
                        $connect = null;
                        $talkStatus['endTalk'] = true;
                    } else {
                        if($input) {
                            $response .= $input;
                            $this->_write($connect, self::STATUS_CLIENT_READING);
                        }
                    }
                    break;
            }
            usleep (50);
        }
        $response = $this->_prepareInput($response);
        if(!$response)
            throw new SocketException('The server did not return a response');
        if($response)
            return $response;

    }

    private function _write($connection, $data) {
        return fwrite($connection, $data.self::STREAM_END_LINE, (self::PACKET_SIZE + strlen(self::STREAM_END_LINE)));
    }

    private function _connectToServers(array $servers) {
        if(count($servers) === 0)
            throw new SocketException('No found settings for connect to backend server(servers)!');
        $liveServers = array();
        foreach($servers as $key => $server) {
            $connect = @stream_socket_client('tcp://'.$server['host'].':'.$server['port'], $errNumber, $errString);
            if($connect) {
                stream_set_blocking($connect, false);
                stream_set_timeout($connect, 0);
                $liveServers[] = array(
                    'connection'   => $connect,
                    'weight'    => $server['weight']
                );
            } else unset($servers[$key]);
        }
        return $liveServers;
    }

    private function _selectConnect() {
        if(count($this->_connectionLine) == 0)
            throw new SocketException('You can\'t connect to any backend server');
        $currentConnection = array_shift ($this->_connectionLine);
        $this->_connectionLine[] = $currentConnection;
        return $currentConnection;
    }

    private function _createConnectionLine(array $servers) {
        $line = array();
        foreach($servers as $server) {
            for($i = 0; $i <= $server['weight']; $i++)
                $line[] = $server['connection'];
        }
        return $line;
    }

    private function _prepareInput($input) {
        return @unserialize(base64_decode($input));
    }

    public function _prepareOutput($data) {
        return @base64_encode(serialize($data));
    }

    public function disconnect($theConnect = null) {
        if($this->_connectionLine)
            foreach($this->_connectionLine as $key => $connect) {
                if(!is_null($theConnect)) {
                    if($theConnect == $connect)
                        unset($this->_connectionLine[$key]);
                } else @fclose($connect);
            }
        if(!is_null($theConnect)) @fclose($theConnect);
    }
}